package compiladorP1.compilador.lexico;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import compiladorP1.compilador.Error;

import compiladorP1.compilador.Token;

/**
 * 
 * @author Carlos Gil Jimenez
 *         Pablo Caloto Crespo
 *
 */
public class Lexer {

	private ArrayList<Token> listaTokens; //acumulado de los tokens leidos
	private String lex;	// Lista de caracteres leidos (lexema)
	private int estado; // Estado en el que se encuentra el automata
	private BufferedReader programa; // Archivo con el programa fuente
	private boolean archivoVacio; // Indica si el programa esta en blanco
	private String lineaActual; // Linea del programa que se acaba de leer en el archivo
	private int longLinea; // Longitud de "lineaActual"
	private int numLinea; // Indica la posicion que ocupa "lineaActual" en el programa
	private char buf; // Siguiente caracter a procesar

	private int posicion; // Indica la posicion de "buf" en la linea
	private int tok;

	/**
	 * Constructor de pruebas
	 */
	public Lexer() {
		super();
		Prueba1();
	}

	/**
	 * Constructor a partir de un BufferedReader
	 */
	public Lexer(BufferedReader programa_) throws Error{

		this.programa = programa_;		

		try {
			lineaActual = programa.readLine(); // Lee la primera linea del programa
			if (lineaActual == null) {
				archivoVacio = true;
				//error de archivo vacio o nada que compilar.
				Error e = new Error("No hay nada que compilar, el archivo esta vacio");
				e.setTipo(Error.SOLOMOTIVO);
				throw e;
			}
			else {
				archivoVacio = false;
				posicion = 0;
				numLinea = 1;
				longLinea = lineaActual.length();
				buf = lineaActual.charAt(posicion);
			}
		}
		catch (IOException exc){
			//informar del error de lectura en la ventana
			Error e = new Error("Se produjo un error en la lectura del fichero");
			e.setTipo(Error.SOLOMOTIVO);
			throw e;
		};

		listaTokens = new ArrayList<Token>();
	}


	/**
	 * Devolvemos el siguiente token, y lo eliminamos de la lista
	 * 
	 * @return the token
	 */
	public Token getSiguienteToken(){
		Token salida=null;

		if (listaTokens.size()>=1){
			salida = listaTokens.get(0);
			listaTokens.remove(0);
		}

		return salida;
	}
	/**
	 * Reconoce el AF
	 * 
	 * @throws Error
	 */
	public void scanner() throws Error{
		//Reinicio de los parametros
		tokenReconocido();

		//Recorremos todo el fichero por lineas
		while (!archivoVacio) {
			//variable necesaria para ignorar lineas comentadas
			boolean ignorar= false;
			//Analizamos cada una de las lineas
			while (posicion <= longLinea) {
				switch (estado) {
				case 0: {
					if ((buf == ' ') || (buf == '\t')){
						transita(0);
						lex = "";
					}	
					else if ((buf == '/')) { //posible comentario
						transita(2);
					}
					else if ((Character.isDigit(buf)) ) {
						transita(3); 
					} 
					else if (Character.isLetter(buf)) {
						transita(1);
					} 
					else if (buf == '>') {
						transita(4);
					}
					else if (buf == '<') {
						transita(5);
					}
					else if (buf == '!') {
						transita(6);
					}
					else if (buf == ':') {
						transita(7);
					}
					else if (buf == '=') {
						transita(9);
					}					
					else if ((buf == ';') || (buf == '+') || (buf == '-')|| (buf == '%') 
							|| (buf == '*')  || (buf == '(') || (buf == ')')|| (buf == '&')|| (buf == '|')) {
						tok = token(Character.toString(buf));
						transita(8);
					}
					else if (buf ==','){
						tok = Token.COMA;
						transita(8);
					}
					else {
						//error lexico
						Error er = new Error(numLinea, ("no se esperaba el caracter " + buf));
						throw er;
					};
					break;
				}
				case 1: {
					if ((Character.isLetterOrDigit(buf)) || (buf == '_') || (buf == '@') || (buf == '$')) {
						transita(1);
					}	
					else {
						tok = tokenReservada(lex);

						Token t;
						if (tok == 0) { //ID
							t = new Token(Token.ID,lex,numLinea);
						}
						else { //
							t = new Token(tok,lex,numLinea);
						};						
						listaTokens.add(t);
						tokenReconocido();
					};
					break;
				}
				case 2: { //estado para comentarios, sino, es un token division
					
					if (ignorar==true){
						posicion += 1;
						lex="";
					}
					else if (buf == '/') {
						ignorar=true;	
					}
					else{
						Token t = new Token(Token.DIV, lex, numLinea);
						listaTokens.add(t);
						tokenReconocido();	
					}
					break;
				}
				case 3: {
					if (Character.isDigit(buf)) {
						transita(3);
					}	
					else {
						Token t = new Token(Token.ENTERO, lex, numLinea);
						listaTokens.add(t);
						tokenReconocido();
					};
					break;
				}
				case 4: {
					if (buf == '=') {
						tok = Token.MAYORIGUALQUE;
						transita(8);
					}
					else {
						Token t = new Token(Token.MAYORQUE, lex, numLinea);
						listaTokens.add(t);
						tokenReconocido();
					};
					break;
				}
				case 5: {
					if (buf == '=') {
						tok = Token.MENORIGUALQUE;
						transita(8);
					}

					else {
						Token t = new Token(Token.MENORQUE, lex, numLinea);
						listaTokens.add(t);
						tokenReconocido();
					};	
					break;
				}
				case 6: {
					if (buf == '=') {
						tok = Token.DISTINTO;
						transita(8);
					}
					else {
						Token t = new Token(Token.NEGACION, lex, numLinea);
						listaTokens.add(t);
						tokenReconocido();
					};
					break;
				}
				case 7: {
					if (buf == '=') {
						tok = Token.ASIGNACION;
						transita(8);
					}

					else if (buf == ':'){
						tok= Token.ASIGNATIPO;
						transita(8);
					}	
					else{
						//fallo, despues de dos puntos se espera '=' o ':'
						Error e = new Error(numLinea,"que despues de ':' se espera '=' o ':' ");
						throw e;
					}
					break;
				}
				case 8: {
					Token t = new Token(tok, lex, numLinea);
					listaTokens.add(t);
					tokenReconocido();
					break;
				}
				case 9: {
					if (buf == '=') {
						tok = Token.IGUAL;
						transita(8);
					}
					else {
						//fallo, despues '=' solo va otro '='
						Error e = new Error(numLinea,"que despues de '=' solo puede ir otro '=' ");
						throw e;
					};
					break;
				}
				} // Fin de switch
			} // Fin de segundo while

			try {	
				lineaActual = programa.readLine();
				if (lineaActual == null) { 
					archivoVacio = true;
				}
				else {
					longLinea = lineaActual.length();
					posicion = 0;
					estado = 0;
					if (longLinea > 0) 
						buf = lineaActual.charAt(posicion);
					else
						buf =' ';
					numLinea += 1;
				};
			}
			catch (IOException exc){
				Error e = new Error("Error al leer el archivo fuente");
				e.setTipo(Error.SOLOMOTIVO);
				throw e;
			};
		} // Fin de primer while
	} // Fin de scanner



	/**
	 * Construye el lexema y almacena en buf el siguiente caracter a leer
	 */ 
	private void transita(int i) {
		lex = lex.concat((new Character(buf)).toString());
		estado = i;
		posicion += 1;
		if (posicion <= longLinea - 1) {
			buf = lineaActual.charAt(posicion);
		}
		else {
			buf =' ';
		}
	}

	/** 
	 * Reinicia los atributos lex y estado cada vez que se encuentra un nuevo token
	 */	
	private void tokenReconocido() {
		lex = "";
		estado = 0;
	}

	/**
	 *  Devuelve el codigo del token
	 * @param t
	 * @return
	 */	
	private int token (String t) {
		int codigoToken;

		if (t.equals("+"))
			codigoToken = Token.SUMA;// Constantes.SUMA;
		else if (t.equals("-"))
			codigoToken = Token.RESTA;// Constantes.RESTA;
		else if (t.equals("*"))
			codigoToken = Token.MUL;//Constantes.MUL;
		else if (t.equals("("))
			codigoToken = Token.PABRE;//Constantes.PA;
		else if (t.equals(")"))
			codigoToken = Token.PCIERRA;//Constantes.PC;
		else if (t.equals(";"))
			codigoToken = Token.PUNTOYCOMA;//Constantes.PYCOMA;
		else if (t.equals("&"))
			codigoToken = Token.AND;//Constantes.PYCOMA;
		else if (t.equals("|"))
			codigoToken = Token.OR;//Constantes.PYCOMA;
		else if (t.equals("%"))
			codigoToken = Token.MOD;//Constantes.PYCOMA;
		else
			codigoToken = Token.NULO; //No deberia llegar aqui jamas

		return codigoToken;
	}

	/** 
	 * Devuelve el codigo del token para palabras reservadas. Si no es una palabra reservada devuelve 0
	 */
	private int tokenReservada (String t) {
		int codigoToken;
		if (t.equals("inicio"))
			codigoToken = Token.RES_INICIO;
		else if (t.equals("fin"))
			codigoToken = Token.RES_FIN;
		else if (t.equals("entero"))
			codigoToken = Token.RES_ENTERO;
		else if (t.equals("booleano"))
			codigoToken = Token.RES_BOOLEANO;
		else if (t.equals("cierto"))
			codigoToken = Token.BOOLEANO;
		else if (t.equals("falso"))
			codigoToken = Token.BOOLEANO;
		else
			codigoToken = 0;

		return codigoToken;
	}
	private void Prueba1(){
		//-----------PRUEBA N1-------------------
		listaTokens = new ArrayList<Token>();
		listaTokens.add(new Token(Token.RES_ENTERO,"entero",1));
		listaTokens.add(new Token(Token.ASIGNATIPO,"::",1));
		listaTokens.add(new Token(Token.ID,"x",1));
		listaTokens.add(new Token(Token.COMA,",",1));
		listaTokens.add(new Token(Token.ID,"y",1));
		listaTokens.add(new Token(Token.COMA,",",1));
		listaTokens.add(new Token(Token.ID,"z",1));
		listaTokens.add(new Token(Token.PUNTOYCOMA,";",1));

		listaTokens.add(new Token(Token.RES_BOOLEANO,"booleano",1));
		listaTokens.add(new Token(Token.ASIGNATIPO,"::",1));
		listaTokens.add(new Token(Token.ID,"b",1));
		listaTokens.add(new Token(Token.COMA,",",1));
		listaTokens.add(new Token(Token.ID,"bb",1));
		listaTokens.add(new Token(Token.PUNTOYCOMA,";",1));

		listaTokens.add(new Token(Token.RES_INICIO,"inicio",2));
		
		listaTokens.add(new Token(Token.ID,"x",3));
		listaTokens.add(new Token(Token.ASIGNACION,":=",3));
		listaTokens.add(new Token(Token.ENTERO,"8",3));
		listaTokens.add(new Token(Token.PUNTOYCOMA,";",3));
		listaTokens.add(new Token(Token.ID,"b",3));
		listaTokens.add(new Token(Token.ASIGNACION,":=",3));
		listaTokens.add(new Token(Token.ID,"x",3));
		listaTokens.add(new Token(Token.MAYORQUE,">",3));
		listaTokens.add(new Token(Token.PABRE,"(",3));
		listaTokens.add(new Token(Token.ID,"x",3));
		listaTokens.add(new Token(Token.SUMA,"+",3));
		listaTokens.add(new Token(Token.ENTERO,"6",3));
		listaTokens.add(new Token(Token.PCIERRA,")",3));
		listaTokens.add(new Token(Token.MUL,"*",3));
		listaTokens.add(new Token(Token.ENTERO,"2",3));
		listaTokens.add(new Token(Token.PUNTOYCOMA,";",3));
		
		listaTokens.add(new Token(Token.RES_FIN,"fin",4));
		//-----------FIN PRUEBA N1-------------------
	}

	
	/**
	 * @return the numLinea
	 */
	public int getNumLinea() {
		return numLinea;
	}

	/**
	 * @return the posicion
	 */
	public int getPosicion() {
		return posicion;
	}
}
